package com.qf.microuser.sign.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.qf.common.core.entity.PointInfo;
import com.qf.common.core.entity.PointRetry;
import com.qf.common.core.entity.PointRule;
import com.qf.common.core.entity.SignInfo;
import com.qf.common.core.enums.PointTypeEnum;
import com.qf.common.core.utils.DateUtils;
import com.qf.common.core.utils.UserUtils;
import com.qf.common.redis.utils.RedisUtil;
import com.qf.microuser.point.param.ContinueDayParam;
import com.qf.microuser.point.service.PointInfoService;
import com.qf.microuser.point.service.PointRetryService;
import com.qf.microuser.point.service.PointRuleService;
import com.qf.microuser.sign.mapper.SignMapper;
import com.qf.microuser.sign.service.SignService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;


/**
 * --- 缘分让我们相遇乱世以外 ---
 *
 * @author SJY
 * 2024/12/30
 */
@Service
public class SignServiceImpl implements SignService {
    @Resource
    private SignMapper signMapper;
    @Resource
    private PointRuleService pointRuleService;

    @Resource
    private PointInfoService pointInfoService;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private ThreadPoolExecutor threadPoolTaskExecutor;

    @Resource
    private PointRetryService pointRetryService;


    @Override
    public void sign() {
        Integer uid = UserUtils.getUid();
        Date date = new Date();
        // 一天只能签到一次
        // select * from sign_info where uid =? and date_format(create_time, '%Y-%m-%d') = date_format(now(), '%Y-%m-%d')
        SignInfo signInfo = signMapper.selectOne(new LambdaQueryWrapper<SignInfo>()
                .eq(SignInfo::getUid, uid)
                .apply(ObjectUtil.isNotEmpty(date), "DATE_FORMAT (create_time,'%Y-%m-%d')={0}", new SimpleDateFormat("yyyy-MM-dd").format(date)));
        if (signInfo != null) {
            throw new RuntimeException("今天已经签到过了");
        }
        // 添加签到记录
        signInfo = new SignInfo();
        signInfo.setUid(uid);
        signMapper.insert(signInfo);

        // 送积分
        // 通过积分规则表获取送的积分值
        PointRule pointRule = pointRuleService.pointRuleByType(PointTypeEnum.POINT_SIGN.getType());
        if (pointRule != null) {
            PointInfo info = new PointInfo();
            info.setUid(uid);
            info.setPointType(PointTypeEnum.POINT_SIGN.getType());
            info.setPoint(pointRule.getPoint());
            pointInfoService.addPointInfo(info);
        }
        // 判断连续签到的天数
        List<String> dayList = signMapper.selectDateByMonth(uid);
        int continueSign = 1;
        if (dayList.size() > 1) {
            int temp = Integer.valueOf(dayList.get(0));
            // int begin = Integer.valueOf(dayList.get(1));
            for (int i = 1; i < dayList.size(); i++) {
                int v = Integer.parseInt(dayList.get(i));
                if (v == temp - 1) {
                    continueSign++;
                    temp = v;
                } else {
                    break;
                }
            }
        }
        // 连续签到送积分
        PointRule pointRule2 = pointRuleService.pointRuleByType(PointTypeEnum.POINT_SIGN_CONTINUE.getType());
        if (pointRule != null) {
            String continueDays = pointRule2.getContinueDays();
            // JSON数组转为java对象列表
            List<ContinueDayParam> continueDayParams = JSON.parseArray(continueDays, ContinueDayParam.class);
            int finalContinueSign = continueSign;
            // 查询是否存在指定的连续签到天数
            continueDayParams.stream()
                    .filter(item -> item.getDays().equals(finalContinueSign))
                    .findFirst()
                    .orElse(null);
            // 如果存在, 额外赠送积分
            if (continueDayParams != null) {
                PointInfo info = new PointInfo();
                info.setUid(uid);
                info.setPointType(PointTypeEnum.POINT_SIGN_CONTINUE.getType());
                info.setPoint(pointRule.getPoint());
                pointInfoService.addPointInfo(info);
            }
        }
    }

    @Override
    public void sign2() {
        Integer uid = UserUtils.getUid();
        // 判断当天是否签到,key值包含日期和用户id
        LocalDate now = LocalDate.now();
        // 获取当前月的日期
        int day = now.getDayOfMonth();
        String yyyyMM = DateUtils.formatLocalDate(now, "yyyyMM");
        // sign:年月:用户id
        String signKey = String.format("sign:%s:%s", yyyyMM, uid);
        // 如果key不存在,生成key并设置动态过期时间
        Boolean exist = redisUtil.keyExist(signKey);
        if (!exist) {
            int second = (now.lengthOfMonth() - day + 1) * 24 * 3600;
            // bitmap本质上是字符串,通过如下方式创建key,并设置过期时间
            redisUtil.setBitTimeout(signKey, 0, false, second);
        }
        // 从redis中获取bitmap的签到信息
        Boolean ret = redisUtil.getBit(signKey, day);
        if (ret) {
            throw new RuntimeException("今天已经签到过了");
        }

        // 如果没有签到,进行签到
        redisUtil.setBit(signKey, day);

        // 送积分
        // 通过积分规则表获取送的积分值
        // 用异步的方式处理签到结果和积分的发放没有关联性,可以并行处理
        // 为了提高签到执行效率,异步的方式送积分,如果积分送失败,进行补偿
        // 一般补偿时,可以将数据先存起来,然后通过定时任务进行处理
        threadPoolTaskExecutor.execute(() -> {
            PointRule pointRule2 = pointRuleService.pointRuleByType(PointTypeEnum.POINT_SIGN.getType());
            if (pointRule2 != null) {
                PointInfo info = new PointInfo();
                info.setUid(uid);
                info.setPointType(PointTypeEnum.POINT_SIGN.getType());
                info.setPoint(pointRule2.getPoint());
                pointInfoService.addPointInfo(info);
                try {
                    pointInfoService.addPointInfo(info);
                } catch (Exception e) {
                    PointRetry pointRetry = new PointRetry();
                    // 对象属性的拷贝,还可以使用第三方的jar
                    BeanUtils.copyProperties(info, pointRetry);
                    // 将处理失败的数据存入补偿表
                    pointRetryService.addPointRetry(pointRetry);
                }
            }
        });
        // 判断连续签到天数
        // 从bitmaps中获取连续签到天数
        Long v = redisUtil.bitfield(signKey, day, 1);
        String s = Long.toBinaryString(v);
        int continueSign = 0;
        for (int i = s.length() - 1; i >= 0; i--) {
            char c = s.charAt(i);
            if (c == '1') {
                continueSign++;
            } else {
                break;
            }
        }

        // 送积分
        PointRule pointRule2 = pointRuleService.pointRuleByType(PointTypeEnum.POINT_SIGN_CONTINUE.getType());
        if (pointRule2 != null) {
            String continueDays = pointRule2.getContinueDays();
            // JSON数组转为java对象列表
            List<ContinueDayParam> continueDayParams = JSON.parseArray(continueDays, ContinueDayParam.class);
            int finalContinueSign = continueSign;
            // 查询是否存在指定的连续签到天数
            continueDayParams.stream()
                    .filter(item -> item.getDays().equals(finalContinueSign))
                    .findFirst()
                    .orElse(null);
            // 如果存在, 额外赠送积分
            if (continueDayParams != null) {
                PointInfo info = new PointInfo();
                info.setUid(uid);
                info.setPointType(PointTypeEnum.POINT_SIGN_CONTINUE.getType());
                info.setPoint(pointRule2.getPoint());
                pointInfoService.addPointInfo(info);
            }
        }
    }
}
